package org.netbeans.modules.xml.schema.hyperlink;

import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import javax.swing.text.Document;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.api.xml.lexer.XMLTokenId;
import org.netbeans.editor.BaseDocument;
import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProviderExt;
import org.netbeans.lib.editor.hyperlink.spi.HyperlinkType;
import org.netbeans.modules.xml.schema.completion.spi.CompletionModelProvider;
import org.netbeans.modules.xml.schema.completion.spi.CompletionModelProvider.CompletionModel;
import org.openide.util.Lookup;

/**
 * Provides hyperlinking for schema based documents
 * 
 * @author Rohan Ranade
 */
public class SchemaBasedHyperlinkProvider implements HyperlinkProviderExt {

    private static final Set<HyperlinkType> SUPPORTED_TYPES
            = EnumSet.of(HyperlinkType.GO_TO_DECLARATION);

    public Set<HyperlinkType> getSupportedHyperlinkTypes() {
        return SUPPORTED_TYPES;
    }

    public boolean isHyperlinkPoint(Document doc, final int offset, HyperlinkType type) {
        if (!(doc instanceof BaseDocument)) {
            return false;
        }
        final boolean[] result = { false };
        final BaseDocument baseDocument = (BaseDocument) doc;
        baseDocument.runAtomic(new Runnable() {
            public void run() {
                result[0] = trIsHyperlinkPoint(baseDocument, offset);
            }
        });
        return result[0];
    }

    public int[] getHyperlinkSpan(Document doc, final int offset, HyperlinkType type) {
        final int[] result = { -1, -1 };
        if (!(doc instanceof BaseDocument)) {
            return result;
        }
        final BaseDocument baseDocument = (BaseDocument) doc;
        baseDocument.runAtomic(new Runnable() {
            public void run() {
                int[] res = trGetHyperlinkSpan(baseDocument, offset);
                result[0] = res[0];
                result[1] = res[1];
            }
        });
        return result;
    }

    public void performClickAction(Document doc, int offset, HyperlinkType type) {
        // nothing at this moment
    }

    public String getTooltipText(Document doc, int offset, HyperlinkType type) {
        return "Sample tooltip text";
    }

    private boolean trIsHyperlinkPoint(BaseDocument baseDocument, int offset) {
        if (!Utilities.canProvideHyperlinks(baseDocument)) {
            return false;
        }
        TokenHierarchy th = TokenHierarchy.get(baseDocument);
        TokenSequence<XMLTokenId> ts = th.tokenSequence();
        ts.move(offset);
        if (!ts.moveNext()) {
            return false;
        }
        Token<XMLTokenId> token = ts.token();
        if (token.id() == XMLTokenId.TAG) {
            return true;
        }
        return false;
    }

    private int[] trGetHyperlinkSpan(BaseDocument baseDocument, int offset) {
        int[] ret = { -1, -1 };
        if (!Utilities.canProvideHyperlinks(baseDocument)) {
            return ret;
        }
        TokenHierarchy th = TokenHierarchy.get(baseDocument);
        TokenSequence<XMLTokenId> ts = th.tokenSequence();
        ts.move(offset);
        if (!ts.moveNext()) {
            return ret;
        }
        Token<XMLTokenId> token = ts.token();
        if (token.id() == XMLTokenId.TAG) {
            ret[0] = token.offset(th);
            ret[1] = ret[0] + token.length();
        }
        return ret;
    }

//    /**
//     * Finds all CompletionModelProviders and builds a model map for schemas having TNS
//     * and builds a list for all no namespace models.
//     */
//    public boolean initModels() {
//        Lookup.Template templ = new Lookup.Template(CompletionModelProvider.class);
//        Lookup.Result result = Lookup.getDefault().lookup(templ);
//        Collection impls = result.allInstances();
//        if(impls == null || impls.size() == 0)
//            return false;
//        //first try all providers
//        for(Object obj: impls) {
//            CompletionModelProvider modelProvider = (CompletionModelProvider)obj;
//            List<CompletionModel> models = modelProvider.getModels(this);
//            if(models == null || models.size() == 0)
//                continue;
//            for(CompletionModel m: models) {
//                populateModelMap(m);
//            }
//        }
//
//        if(noNamespaceSchemaLocation != null && noNSModels.size() == 1) {
//            noNamespaceModel = noNSModels.get(0);
//        }
//
//        //last resort: try special completion
//        if(nsModelMap.size() == 0 && noNSModels.size() == 0)
//            specialCompletion();
//
//        return !(nsModelMap.size() == 0 && noNSModels.size() == 0);
//    }
//
//    private void populateModelMap(CompletionModel m) {
//        String tns = m.getSchemaModel().getSchema().getTargetNamespace();
//        if (tns == null) {
//            noNSModels.add(m); //no namespace models
//            return;
//        }
//        //models with namespaces
//        nsModelMap.put(tns, m);
//    }
//
//    /**
//     *  Special completion comes into play when all register providers fail.
//     *  And we try to provide completion for docs like project.xml.
//     */
//    private void specialCompletion() {
//        //instance documents with neither schemaLocation nor
//        //noNamespaceSchemaLocation attribute, e.g. project.xml
//        if(primaryFile == null)
//            return;
//
//        specialCompletion = true;
//        specialNamespaceMap = CompletionUtil.getNamespacesFromStartTags(document);
//        for(String temp : specialNamespaceMap.keySet()) {
//            try {
//                DefaultModelProvider provider = new DefaultModelProvider(this);
//                CompletionModel cm = provider.getCompletionModel(new java.net.URI(temp), false);
//                populateModelMap(cm);
//            } catch (Exception ex) {
//                logger.log(Level.INFO, null, ex);
//                //continue with the next one.
//                continue;
//            }
//        }
//    }
}
